package com.dyp.oauth2_demo.config;

import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.time.Duration;
import java.util.UUID;

@Configuration
//@EnableWebSecurity
public class AuthorizationServerConfig {
    @Bean
//  因为有多个SecurityFilterChain配置，所以通过 @Order(数字) 显式指定顺序
    @Order(Ordered.HIGHEST_PRECEDENCE)
    SecurityFilterChain authrorizationServerFilterChain(HttpSecurity http) throws Exception{
//        For removal in 2.0. Use HttpSecurity.with(SecurityConfigurerAdapter, Customizer)
//        and pass in OAuth2AuthorizationServerConfigurer.authorizationServer().
//        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);

        // ============ 新配置替换上面的applyDefaultSecurity(http)老式配置 ====================
        OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
                OAuth2AuthorizationServerConfigurer.authorizationServer();
        http
                .securityMatcher(authorizationServerConfigurer.getEndpointsMatcher())
                .with(authorizationServerConfigurer, Customizer.withDefaults())
                .authorizeHttpRequests((authorize) ->
                        authorize.requestMatchers("/oauth2/**").permitAll()
                                .anyRequest().authenticated()
                );
        // ========================  end =============================================

        // 开启OIDC
        http.getConfigurer(OAuth2AuthorizationServerConfigurer.class).oidc(Customizer.withDefaults());

        http
                //接受用户信息和/或客户端注册的访问令牌
                .oauth2ResourceServer((oauth2)-> oauth2.jwt(Customizer.withDefaults()));


        return http.build();
    }

    /**
     * 配置密码解析器，使用BCrypt的方式对密码进行加密和验证
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 管理客户端:
     * 用户在获取到code后，通过code获取access_token等数据时需要使用clientId clientSecret的数据
     *
     * 下面代码中：
     * 基于内存模式（InMemory）构建了一个oidc-client客户端，客户端通过请求头的形式进行认证，并支持授权码、刷新码、客户端三种认证方式，
     * 通过tokenSettings将access_token的有效期设置成2小时。
     * @param passwordEncoder 密码管理器
     */
    @Bean
    public RegisteredClientRepository registeredClientRepository(PasswordEncoder passwordEncoder) {
        RegisteredClient client = RegisteredClient.withId(UUID.randomUUID().toString())
                .clientId("my-client")  // 客户端ID
                .clientSecret(passwordEncoder.encode("123456"))  // 客户端密钥，获取授权码的时候需要和code一起带回
                .clientAuthenticationMethod(ClientAuthenticationMethod.NONE)  //  客户端凭证（client_id 和 client_secret）的传递方式。CLIENT_SECRET_BASIC：客户端凭证放在 HTTP 请求头 Authorization: Basic <Base64(client_id:client_secret)>
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)  // 支持授权码模式
                .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)  // 持刷新令牌
                .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) //支持客户端模式：机器对机器(M2M)访问（比如服务间调用）
                .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE)
                .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) //OAuth 2.0 Device Authorization Grant（设备授权流） 的实现，适用于 无浏览器或输入受限的设备（如智能电视、IoT设备、CLI工具等）
                .redirectUri("http://127.0.0.1:8080/login/oauth2/code/my-client")  // 回调地址，需要和请求code时候填写的回调地址一致
                .postLogoutRedirectUri("http://127.0.0.1:8080/")
                .scope(OidcScopes.OPENID)
                .scope(OidcScopes.PROFILE)
                .scope("user.info")
                .scope("read")
                .scope("all")  // 授权范围
                .clientSettings(ClientSettings.builder().requireProofKey(true).build())   // 需要用户授权，有一个单独的确认页面，用户需要确认后才会发放code
                .tokenSettings(TokenSettings.builder().accessTokenTimeToLive(Duration.ofHours(2)).build())
                .build();

        return new InMemoryRegisteredClientRepository(client);  // 内存存储
    }

    /**
     * 创建RsaKey
     */
    private static KeyPair generateRsaKey(){
        KeyPair keyPair;
        try{
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
            keyPairGenerator.initialize(2048);
            keyPair = keyPairGenerator.generateKeyPair();
        }catch (Exception ex){
            throw new IllegalStateException(ex);
        }
        return keyPair;
    }

    /**
     * 用于签署访问令牌
     */
    @Bean
    public JWKSource<SecurityContext> jwkSource(){
        KeyPair keyPair = generateRsaKey();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        RSAKey rsaKey = new RSAKey.Builder(publicKey)
                .privateKey(privateKey)
                .keyID(UUID.randomUUID().toString())
                .build();
        JWKSet jwkSet = new JWKSet(rsaKey);
        return new ImmutableJWKSet<>(jwkSet);
    }


    /**
     * 解码签名访问令牌
     */
    @Bean
    public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource){
        return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
    }

    @Bean
    public AuthorizationServerSettings authorizationServerSettings(){
        return AuthorizationServerSettings.builder().build();
    }

//    为了支持 Device Code模式
    @Bean
    public OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer() {
        return context -> {
            if (context.getTokenType().getValue().equals("device_code"))  {
                // 自定义 Device Code 的 Claims（如附加设备信息）
                context.getClaims().claim("device_type",  "smart_tv");
            }
        };
    }

}
